# vim:ts=4:sw=4:noet:
import unittest
from c.compiler.parse import Parser
from c.compiler.mem import Memory
from c.compiler.semantics import Checker
from c.compiler.codegen import CodeGen, Bytecodes
from c.compiler.optimise import Optimiser
from c.compiler.disassemble import disassembleMethod
import c.compiler.spy as spy
from c.base.support import Status

NIL = 0xD
ADD = 2
def PUSHT(x): return (Bytecodes.PushTemporary<<4)|x
def PUSHC(x): return (Bytecodes.PushConstant<<4)|x
def PUSHB(x,y): return [(Bytecodes.PushBlock<<4)|x, y]
def SEND0(x): return (Bytecodes.SendMessage0<<4)|x
def SEND1(x): return (Bytecodes.SendMessage1<<4)|x
def SEND2(x): return (Bytecodes.SendMessage2<<4)|x
def SENDB(x): return (Bytecodes.SendBinary<<4)|x
def POPIT(x): return (Bytecodes.PopInstTemp<<4)|x
def STORT(x): return (Bytecodes.AssignTemporary<<4)|x
def BRNCHF(x): return [(Bytecodes.DoSpecial<<4)|Bytecodes.SpecialBranchFalse, x]
def RETST(): return (Bytecodes.DoSpecial<<4)|Bytecodes.SpecialReturnStack
def RETSF(): return (Bytecodes.DoSpecial<<4)|Bytecodes.SpecialReturnSelf
def POP(): return (Bytecodes.DoSpecial<<4)|Bytecodes.SpecialPop

class CodegenTests(unittest.TestCase):
	def setUp(self):
		stat = Status({"exceptions":True})
		self.p = Parser(None, stat)
		classes = []
		self.m = Memory(stat)
		self.m.createBase()
		self.s = Checker(self.m, stat)
		self.s.populateFromMem()
		self.cg = CodeGen(self.m)

	def compileMethod(self, code):
		meth = self.p.parseMethodFromSource(code)
		cls = self.m.globals["mainClass"]
		cls.methods = spy.SpyDictionary(self.m)
		self.s.checkSingleMethod(cls, meth)
		self.cg.setup()
		opt = Optimiser(self, self.m, [cls])
		opt.doIROpts(cls.name, meth)
		meth.cfg()
		opt.doBBOpts(cls.name, meth)
		meth.cg(self.m, self.cg)
		self.cg.fixLabels()
		if self.cg.seenSuper:
			self.cg.literals.append(cls)
		return self.cg.bcs, self.cg.literals

	def makeBytecode(self, ibc):
		bc = []
		for a in ibc:
			if isinstance(a, int):
				bc.append(a)
			elif isinstance(a, list):
				bc.extend(a)
			else:
				bc.append(a())
		return bc
		
	def checkMethod(self, code, ebcs):
		bc, lits = self.compileMethod(code)
		ebc = self.makeBytecode(ebcs)
		self.assertEqual(bc, ebc, "Bytecode mismatch.\nExpected:\n%s\n\nGot:\n%s" % (
			disassembleMethod(ebc, lits)[0], disassembleMethod(bc, lits)[0]))

    def testBasicIf(self):
		self.checkMethod(
			"foo: x [ x > 0 ifTrue: [ ^ x negate. ]. ^ x ]", [
			PUSHT(1), 
			PUSHC(0), 
			SEND1(0), 
			BRNCHF(8), 
			PUSHT(1), 
			SEND0(1), 
			RETST, 
			PUSHC(NIL),
			POP, 
			PUSHT(1), 
			RETST
		])
    
	def testToDo(self):
		self.checkMethod(
			"""
			foo: max [ 
				|sum| 
				sum := 0. 
				1 to: max do: [ :i | 
					sum := sum + i. 
				]. 
			]""", [
			PUSHC(0), 
			POPIT(10), 
			PUSHC(1),
			PUSHT(1),
			PUSHB(3, 11),
			  PUSHT(2),
			  PUSHT(3),
			  SENDB(ADD),
			  STORT(2),
			  RETST,
			SEND2(0), 
			POP, 
			RETSF
		])
